/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.admin.service.impl;

import vip.dexian.admin.assembler.AdminCommandAssembler;
import vip.dexian.admin.assembler.ProfileCommandAssembler;
import vip.dexian.admin.command.AdminRoleUpdateCommand;
import vip.dexian.admin.command.AdminSaveCommand;
import vip.dexian.admin.command.AdminUpdateCommand;
import vip.dexian.admin.command.PasswordUpdateCommand;
import vip.dexian.admin.command.ProfileUpdateCommand;
import vip.dexian.admin.dao.AdminDao;
import vip.dexian.admin.dao.DepartmentDao;
import vip.dexian.admin.dao.RoleDao;
import vip.dexian.admin.dto.AdminDTO;
import vip.dexian.admin.dto.AdminPrincipal;
import vip.dexian.admin.entity.Admin;
import vip.dexian.admin.entity.Department;
import vip.dexian.admin.entity.Role;
import vip.dexian.admin.event.AdminEnableDisableEvent;
import vip.dexian.admin.service.AdminService;
import vip.dexian.admin.service.OnlineAdminService;
import vip.dexian.common.query.Filter;
import vip.dexian.common.query.Page;
import vip.dexian.common.query.Pageable;
import vip.dexian.common.utils.CollectionUtils;
import vip.dexian.common.utils.FilterUtils;
import vip.dexian.common.utils.LocalDateTimeUtils;
import vip.dexian.core.base.event.DomainEventPublisher;
import vip.dexian.core.base.service.BaseServiceImpl;
import vip.dexian.core.base.validation.group.Save;
import vip.dexian.core.base.validation.group.Update;
import vip.dexian.core.exception.ServiceValidationException;
import vip.dexian.core.security.PermsGrantedAuthority;
import vip.dexian.password.encoder.PasswordEncoder;
import vip.dexian.service.datascope.DataScope;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 管理员业务逻辑处理实现
 *
 * @author 挺好的 2023年06月05日 08:45:00
 */
@Service ("adminServiceImpl")
@Slf4j
public class AdminServiceImpl extends BaseServiceImpl <Admin, AdminDTO, Long> implements AdminService {

    /**
     * 管理员Dao
     */
    @Resource (name = "adminDaoImpl")
    private AdminDao adminDao;

    /**
     * 角色dao
     */
    @Resource (name = "roleDaoImpl")
    private RoleDao roleDao;

    /**
     * 部门
     */
    @Resource (name = "departmentDaoImpl")
    private DepartmentDao departmentDao;

    /**
     * 密码加密
     */
    @Resource (name = "passwordEncoder")
    private PasswordEncoder passwordEncoder;

    /**
     * command -> entity
     */
    @Resource (name = "adminCommandAssemblerImpl")
    private AdminCommandAssembler adminCommandAssembler;

    /**
     * profile -> entity
     */
    @Resource (name = "profileCommandAssemblerImpl")
    private ProfileCommandAssembler profileCommandAssembler;

    /**
     * 在线管理员
     */
    @Resource (name = "onlineAdminServiceImpl")
    private OnlineAdminService onlineAdminService;

    /**
     * 领域事件发布
     */
    @Autowired
    private DomainEventPublisher domainEventPublisher;

    /**
     * 保存管理员信息
     *
     * @param command
     *         保存指令
     *
     * @return {@link Admin}
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public Admin save (AdminSaveCommand command) {
        log.info("save command:{}", command);
        Admin admin = this.adminCommandAssembler.toSave(command, Save.class);

        String username = admin.getUsername();

        // username 和 mobile的校验，需要调用checkXXX方法，如果调用isXXXExists，该方法会加入到当前的事物中
        if (this.checkUsername(username)) {
            throw new ServiceValidationException("A30001");
        }

        String mobile = admin.getMobile();

        if (this.checkMobile(mobile)) {
            throw new ServiceValidationException("A30002");
        }

        this.checkDepartment(command.getDepartmentId());

        Set <Role> roles = new HashSet <>(this.roleDao.findList(command.getRoleIds()));
        admin.setRoles(roles);
        admin.setPassword(this.passwordEncoder.getPasswordEncoder().encode(admin.getPassword()));

        return super.save(admin);
    }

    /**
     * 修改管理员信息
     *
     * @param command
     *         修改指令
     *
     * @return {@link Admin}
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public Admin update (AdminUpdateCommand command) {
        log.info("update command:{}", command);
        Admin admin = this.adminCommandAssembler.toUpdate(command, Update.class);

        this.adminDao.findByValidationIfNull(command.getId(), "管理员");

        this.checkDepartment(command.getDepartmentId());

        return super.update(admin);
    }

    /**
     * 启用或者禁用
     *
     * @param id
     *         管理员id
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void enabledOrDisabled (Long id) {
        Admin admin = this.adminDao.findByValidationIfNull(id, "管理员");
        admin.setIsEnabled(!admin.getIsEnabled());
        // 记录原来的值，修改后的值，操作人员，
        //        SensitiveLog.builder().adminId().
        this.domainEventPublisher.publishEvent(new AdminEnableDisableEvent(admin));
    }

    /**
     * 锁定或者解锁
     *
     * @param id
     *         管理员id
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void lockOrUnlock (Long id) {
        Admin admin = this.adminDao.findByValidationIfNull(id, "管理员");

        // 如果未锁定，此时要锁定
        if (Boolean.TRUE.equals(admin.getIsAccountNonLocked())) {
            admin.setIsAccountNonLocked(false);
            admin.setLockedDate(LocalDateTime.now());
        } else {
            admin.setLoginFailureCount(0);
            admin.setIsAccountNonLocked(true);
            admin.setLockedDate(null);
        }

        // TODO 增加敏感操作日志
    }

    /**
     * 修改密码
     *
     * @param id
     *         管理员id
     * @param password
     *         新密码
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void modifyPassword (Long id, String password) {
        log.info("modify password parameter id:{}", id);

        if (StringUtils.isEmpty(password)) {
            throw new ServiceValidationException("A30003");
        }

        Admin admin = this.adminDao.findByValidationIfNull(id, "管理员");
        admin.setPassword(this.passwordEncoder.getPasswordEncoder().encode(password));

        // TODO 增加敏感操作日志
    }


    /**
     * 修改手机号码
     *
     * @param id
     *         管理员id
     * @param mobile
     *         新手机号码
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void modifyMobile (Long id, String mobile) {
        log.info("modify mobile parameter id:{}, mobile:{}", id, mobile);
        if (StringUtils.isEmpty(mobile)) {
            throw new ServiceValidationException("A30004");
        }

        Admin admin = this.adminDao.findByValidationIfNull(id, "管理员");

        if (StringUtils.equals(admin.getMobile(), mobile)) {
            throw new ServiceValidationException("A30005");
        }

        if (this.checkMobile(mobile)) {
            throw new ServiceValidationException("A30002");
        }

        admin.setMobile(mobile);

        // TODO 增加敏感操作日志
    }

    /**
     * 修改角色
     *
     * @param command
     *         修改角色command
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void modifyRole (AdminRoleUpdateCommand command) {
        log.info("modify role command: {}", command);

        super.validation(command);

        Admin admin = this.adminDao.findByValidationIfNull(command.getId(), "管理员");

        Set <Role> roles = new HashSet <>(this.roleDao.findList(command.getRoleIds()));
        admin.setRoles(roles);

        // TODO 增加敏感操作日志
    }


    /**
     * 修改当前登录账号的密码
     *
     * @param command
     *         修改指令
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void modifyPassword (PasswordUpdateCommand command) {
        log.info("modify password command: {}", command);

        super.validation(command);

        AdminPrincipal admin = this.getCurrent();

        if (admin == null) {
            return;
        }

        String oldPassword = command.getOldPassword();
        String newPassword = command.getNewPassword();

        // 新密码和原密码一致
        if (StringUtils.equals(oldPassword, newPassword)) {
            throw new ServiceValidationException("A30006");
        }

        if (this.isOldPasswordNonValid(oldPassword)) {
            throw new ServiceValidationException("A30007");
        }

        Admin dbAdmin = this.adminDao.findByValidationIfNull(admin.getId(), "管理员");

        dbAdmin.setPassword(this.passwordEncoder.getPasswordEncoder().encode(newPassword));

        this.onlineAdminService.logoutByUsername(dbAdmin.getUsername());

    }

    /**
     * 修改当前登录账号的个人信息
     *
     * @param command
     *         修改指令
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void modifyProfile (ProfileUpdateCommand command) {
        log.info("profile update command: {}", command);

        AdminPrincipal admin = this.getCurrent();

        if (admin == null) {
            return;
        }

        Admin convertAdmin = this.profileCommandAssembler.toEntity(command);
        convertAdmin.setId(admin.getId());

        super.update(convertAdmin);
    }


    /**
     * 查找管理员
     *
     * @param pageable
     *         搜索条件，包含客户端的搜索信息以及分页信息
     *
     * @return {@link vip.dexian.admin.dto.AdminDTO}
     */
    @DataScope
    @Override
    @Transactional (readOnly = true, propagation = Propagation.SUPPORTS)
    public Page <AdminDTO> findPage (Pageable pageable) {
        Page <AdminDTO> page = super.findPage(pageable);
        this.onlineAdminService.fillOnline(page);
        return page;
    }

    /**
     * 获取当前登录的管理员
     *
     * @return {@link AdminPrincipal}
     */
    @Override
    @Transactional (propagation = Propagation.NOT_SUPPORTED)
    public AdminPrincipal getCurrent () {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            return null;
        }

        if (authentication instanceof UsernamePasswordAuthenticationToken) {
            return (AdminPrincipal) authentication.getPrincipal();
        }

        return null;
    }


    /**
     * 查找当前登录的管理员
     *
     * @return {@link vip.dexian.admin.dto.AdminDTO}
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public AdminDTO findCurrent () {

        AdminPrincipal adminPrincipal = this.getCurrent();

        if (adminPrincipal == null) {
            return null;
        }

        return this.find(adminPrincipal.getId());
    }

    /**
     * 原密码是否正确
     *
     * @param oldPassword
     *         原密码
     *
     * @return 原密码是否正确
     */
    @Override
    @Transactional (propagation = Propagation.NOT_SUPPORTED)
    public boolean isOldPasswordNonValid (String oldPassword) {
        if (StringUtils.isEmpty(oldPassword)) {
            return true;
        }

        AdminPrincipal admin = this.getCurrent();

        return !this.passwordEncoder.getPasswordEncoder().matches(oldPassword, admin.getPassword());
    }

    /**
     * 是否已认证
     *
     * @return 是否认证
     */
    @Override
    @Transactional (propagation = Propagation.NOT_SUPPORTED)
    public boolean isAuthenticated () {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            return false;
        }

        if (authentication instanceof UsernamePasswordAuthenticationToken) {
            return authentication.isAuthenticated();
        }

        return false;
    }

    /**
     * 是否有权限
     *
     * @param name
     *         权限名称
     *
     * @return 1. 权限名称为空 {@link org.apache.commons.lang3.StringUtils#isBlank(CharSequence)}。2.未授权。3.没有权限返回false。否则返回true
     */
    @Override
    @Transactional (propagation = Propagation.NOT_SUPPORTED)
    public boolean hasPermission (String name) {
        if (StringUtils.isBlank(name)) {
            return false;
        }

        AdminPrincipal adminPrincipal = this.getCurrent();

        Collection <? extends GrantedAuthority> authorities = adminPrincipal.getAuthorities();

        for (GrantedAuthority grantedAuthority : authorities) {

            if (grantedAuthority instanceof PermsGrantedAuthority permsGrantedAuthority) {
                // 获取权限
                List <String> permsAuthorities = permsGrantedAuthority.getAuthorities();
                if (permsAuthorities.contains(name)) {
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * 根据id批量删除管理员
     *
     * @param ids
     *         id列表
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void delete (Long... ids) {
        log.info("delete parameter:{}", ids);

        this.validDelete(ids);

        // 去重复并且去除<=0的id
        List <Long> idsList = CollectionUtils.distinct(ids);

        for (Long id : idsList) {
            Admin admin = this.adminDao.findByValidationIfNull(id, "管理员");

            admin.setUniqueTimestamp(LocalDateTimeUtils.nowToTimestamp());

            admin.getRoles().clear();

            super.delete(admin);

            this.onlineAdminService.logoutByUsername(admin.getUsername());
        }

    }


    /**
     * 根据id查找管理员，并转换为DTO
     *
     * @param id
     *         管理员id
     *
     * @return {@link AdminDTO}
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public AdminDTO find (Long id) {

        AdminDTO adminDTO = super.find(id);

        // 设置角色ID
        if (adminDTO != null) {
            List <Long> roleIds = this.roleDao.findIdByAdmin(id);
            adminDTO.setRoleIds(roleIds);
        }

        return adminDTO;
    }


    /**
     * 用户名是否存在
     *
     * @param username
     *         用户名
     *
     * @return username==null,return true.  返回是否在数据库中存在
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public boolean isUsernameExists (String username) {
        return this.checkUsername(username);
    }

    /**
     * 手机号码是否存在
     *
     * @param mobile
     *         手机号码
     *
     * @return mobile==null,return true.  返回是否在数据库中存在
     */
    @Override
    @Transactional (propagation = Propagation.SUPPORTS, readOnly = true)
    public boolean isMobileExists (String mobile) {
        return this.checkMobile(mobile);
    }

    /**
     * 校验部门信息
     *
     * @param departmentId
     *         部门id
     */
    private void checkDepartment (Long departmentId) {

        Department department = this.departmentDao.find(departmentId);

        if (department == null || !department.getIsEnabled()) {
            throw new ServiceValidationException("A10004");
        }
    }

    /**
     * 校验用户名是否存在
     *
     * @param username
     *         用户名
     *
     * @return 当username is empty || username exists 返回true，否则返回false
     */
    private boolean checkUsername (String username) {
        if (StringUtils.isEmpty(username)) {
            return true;
        }

        List <Filter> filters = FilterUtils.builder().equals("username", username).uniqueKey().build();
        return this.adminDao.exists(filters);
    }

    /**
     * 校验用户名是否存在
     *
     * @param mobile
     *         手机号码
     *
     * @return 当 mobile is empty || username exists 返回true，否则返回false
     */
    private boolean checkMobile (String mobile) {
        if (StringUtils.isEmpty(mobile)) {
            return true;
        }

        List <Filter> filters = FilterUtils.builder().equals("mobile", mobile).uniqueKey().build();
        return this.adminDao.exists(filters);
    }


}
